package com.marshalchen.common.ui.floatingactionbutton;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.annotation.DrawableRes;
import android.util.AttributeSet;
import android.widget.ImageButton;
import com.marshalchen.common.R;

public class FloatingActionButton extends ImageButton {

  public static final int SIZE_NORMAL = 0;
  public static final int SIZE_MINI = 1;

  private static final int HALF_TRANSPARENT_WHITE = Color.argb(128, 255, 255, 255);
  private static final int HALF_TRANSPARENT_BLACK = Color.argb(128, 0, 0, 0);

  int mColorNormal;
  int mColorPressed;
  @DrawableRes
  private int mIcon;
  private int mSize;

  private float mCircleSize;
  private float mShadowRadius;
  private float mShadowOffset;
  private int mDrawableSize;

  public FloatingActionButton(Context context) {
    this(context, null);
  }

  public FloatingActionButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
  }

  public FloatingActionButton(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context, attrs);
  }

  void init(Context context, AttributeSet attributeSet) {
    mColorNormal = getColor(android.R.color.holo_blue_dark);
    mColorPressed = getColor(android.R.color.holo_blue_light);
    mIcon = 0;
    mSize = SIZE_NORMAL;
    if (attributeSet != null) {
      initAttributes(context, attributeSet);
    }

    mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini);
    mShadowRadius = getDimension(R.dimen.fab_shadow_radius);
    mShadowOffset = getDimension(R.dimen.fab_shadow_offset);
    mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius);

    updateBackground();
  }

  int getColor(@ColorRes int id) {
    return getResources().getColor(id);
  }

  float getDimension(@DimenRes int id) {
    return getResources().getDimension(id);
  }

  private void initAttributes(Context context, AttributeSet attributeSet) {
    TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatActionButton, 0, 0);
    if (attr != null) {
      try {
        mColorNormal = attr.getColor(R.styleable.FloatActionButton_colorNormal, getColor(android.R.color.holo_blue_dark));
        mColorPressed = attr.getColor(R.styleable.FloatActionButton_colorPressed, getColor(android.R.color.holo_blue_light));
        mSize = attr.getInt(R.styleable.FloatActionButton_size, SIZE_NORMAL);
        mIcon = attr.getResourceId(R.styleable.FloatActionButton_icon, 0);
      } finally {
        attr.recycle();
      }
    }
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(mDrawableSize, mDrawableSize);
  }

  void updateBackground() {
    float circleLeft = mShadowRadius;
    float circleTop = mShadowRadius - mShadowOffset;

    final RectF circleRect = new RectF(circleLeft, circleTop, circleLeft + mCircleSize, circleTop + mCircleSize);

    LayerDrawable layerDrawable = new LayerDrawable(
        new Drawable[] {
            getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.floating_action_button_fab_bg_normal : R.drawable.floating_action_button_fab_bg_mini),
            createFillDrawable(circleRect),
            createStrokesDrawable(circleRect),
            getIconDrawable()
        });

    float iconOffset = (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2f;

    int iconInsetHorizontal = (int) (mShadowRadius + iconOffset);
    int iconInsetTop = (int) (circleTop + iconOffset);
    int iconInsetBottom = (int) (mShadowRadius + mShadowOffset + iconOffset);

    layerDrawable.setLayerInset(3, iconInsetHorizontal, iconInsetTop, iconInsetHorizontal, iconInsetBottom);

    setBackgroundCompat(layerDrawable);
  }

  Drawable getIconDrawable() {
    if (mIcon != 0) {
      return getResources().getDrawable(mIcon);
    } else {
      return new ColorDrawable(Color.TRANSPARENT);
    }
  }

  private StateListDrawable createFillDrawable(RectF circleRect) {
    StateListDrawable drawable = new StateListDrawable();
    drawable.addState(new int[] { android.R.attr.state_pressed }, createCircleDrawable(circleRect, mColorPressed));
    drawable.addState(new int[] { }, createCircleDrawable(circleRect, mColorNormal));
    return drawable;
  }

  private Drawable createCircleDrawable(RectF circleRect, int color) {
    final Bitmap bitmap = Bitmap.createBitmap(mDrawableSize, mDrawableSize, Config.ARGB_8888);
    final Canvas canvas = new Canvas(bitmap);

    final Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setColor(color);

    canvas.drawOval(circleRect, paint);

    return new BitmapDrawable(getResources(), bitmap);
  }

  private int opacityToAlpha(float opacity) {
    return (int) (255f * opacity);
  }

  private Drawable createStrokesDrawable(RectF circleRect) {
    final Bitmap bitmap = Bitmap.createBitmap(mDrawableSize, mDrawableSize, Config.ARGB_8888);
    final Canvas canvas = new Canvas(bitmap);

    final float strokeWidth = getDimension(R.dimen.fab_stroke_width);
    final float halfStrokeWidth = strokeWidth / 2f;

    RectF outerStrokeRect = new RectF(
        circleRect.left - halfStrokeWidth,
        circleRect.top - halfStrokeWidth,
        circleRect.right + halfStrokeWidth,
        circleRect.bottom + halfStrokeWidth
    );

    RectF innerStrokeRect = new RectF(
        circleRect.left + halfStrokeWidth,
        circleRect.top + halfStrokeWidth,
        circleRect.right - halfStrokeWidth,
        circleRect.bottom - halfStrokeWidth
    );

    final Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setStrokeWidth(strokeWidth);
    paint.setStyle(Style.STROKE);

    // outer
    paint.setColor(Color.BLACK);
    paint.setAlpha(opacityToAlpha(0.02f));
    canvas.drawOval(outerStrokeRect, paint);

    // inner bottom
    paint.setShader(new LinearGradient(innerStrokeRect.centerX(), innerStrokeRect.top, innerStrokeRect.centerX(), innerStrokeRect.bottom,
        new int[] { Color.TRANSPARENT, HALF_TRANSPARENT_BLACK, Color.BLACK },
        new float[] { 0f, 0.8f, 1f },
        TileMode.CLAMP
    ));
    paint.setAlpha(opacityToAlpha(0.04f));
    canvas.drawOval(innerStrokeRect, paint);

    // inner top
    paint.setShader(new LinearGradient(innerStrokeRect.centerX(), innerStrokeRect.top, innerStrokeRect.centerX(), innerStrokeRect.bottom,
        new int[] { Color.WHITE, HALF_TRANSPARENT_WHITE, Color.TRANSPARENT },
        new float[] { 0f, 0.2f, 1f },
        TileMode.CLAMP
    ));
    paint.setAlpha(opacityToAlpha(0.8f));
    canvas.drawOval(innerStrokeRect, paint);

    return new BitmapDrawable(getResources(), bitmap);
  }

  @SuppressWarnings("deprecation")
  @SuppressLint("NewApi")
  private void setBackgroundCompat(Drawable drawable) {
    if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
      setBackground(drawable);
    } else {
      setBackgroundDrawable(drawable);
    }
  }
}
